function Directory() {
    this.url = prefs.getString("url");
    this.navigation = prefs.getString("navigation");
    this.navSelectedColor = prefs.getString("navSelectedColor");
    this.navSelectedBackgroundColor = prefs.getString("navSelectedBackgroundColor");
    this.scrollBy = prefs.getString("scrollBy");
    this.scrollDirection = prefs.getString("scrollDirection");
    this.spacing = parseInt(prefs.getInt("spacing") / 2) + "px";
    this.scrollbarThickness = prefs.getInt("scrollbarThickness");
    this.scrollbarColor = prefs.getString("scrollbarColor");
    this.interval = prefs.getInt("interval") * 60;	//seconds
    this.layout = prefs.getString("layout");
    this.layoutURL = prefs.getString("layoutURL");
    
    if (this.layout == "logos") {
	if (this.isVerticalLayout()) {
	    this.layoutURL = "http://c558478.r78.cf2.rackcdn.com/Vertical/Logos.xml";
	    //this.layoutURL = "http://risegadgets.googlecode.com/svn/test/Directory/Vertical/Logos.xml";
	}
    }
    else if (this.layout == "noLogos") {
	if (this.isVerticalLayout()) {
	    this.layoutURL = "http://c558478.r78.cf2.rackcdn.com/Vertical/NoLogos.xml";
	    //this.layoutURL = "http://risegadgets.googlecode.com/svn/test/Directory/Vertical/NoLogos.xml";
	}
    }
    else {	//Custom
	if (!this.layoutURL) {	//No Layout URL specified.
	    if (this.isVerticalLayout()) {
		this.layoutURL = "http://c558478.r78.cf2.rackcdn.com/Vertical/Logos.xml";
		//this.layoutURL = "http://risegadgets.googlecode.com/svn/test/Directory/Vertical/Logos.xml";
	    }
	}
    }
    
    this.isCustomLayout = (this.layout == "custom" && prefs.getString("layoutURL")) ? true : false;
    this.isLoading = true;
    
    if (this.layout == "logos") {
	this.sortIndex = 1;	//Default sort is on the second column.
    }
    else {
	this.sortIndex = 0;
    }
    
    this.sortDirection = "asc";
}
//Load the layout and CSS files.
Directory.prototype.initialize = function() {
    var self = this;
    var params = {};
    var link = $("<link>");
    
    //Load CSS file for a horizontal table layout.
    if (!this.isCustomLayout && !this.isVerticalLayout()) {
	loadCSS("http://c558478.r78.cf2.rackcdn.com/Horizontal/Horizontal.css");
	//loadCSS("http://risegadgets.googlecode.com/svn/test/Directory/Horizontal/Horizontal.css");
    }
    
    if (this.layoutURL) {
	//Load XML layout.
	params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.DOM;
	gadgets.io.makeRequest(this.layoutURL, function(obj) {
	    var data = obj.data;
	    
	    if (data.getElementsByTagName("Style").length > 0) {
		var head = document.getElementsByTagName("head")[0],
		    style = document.createElement("style");
		    
		style.type = "text/css";
		style.innerHTML = data.getElementsByTagName("Style")[0].childNodes[1].nodeValue;
		head.appendChild(style);
	    }
	    
	    if (data.getElementsByTagName("Directory").length > 0) {
		$("#container").append(data.getElementsByTagName("Directory")[0].childNodes[1].nodeValue);
	    }
	    
	    self.configureNavigation($("#container"));
	    
	    if (data.getElementsByTagName("DirectoryListing").length > 0) {	//Backward compatability.
		self.directoryListing = data.getElementsByTagName("DirectoryListing")[0].childNodes[1].nodeValue;
	    }
	    else {
		self.directoryListing = data.getElementsByTagName("Layout")[0].childNodes[1].nodeValue;
	    }
	    
	    self.getListings("all");
	}, params);
    }
    else {
	this.getListings("all");
    }
}
Directory.prototype.configureNavigation = function($element) {
    var self = this;
    
    $element.alphaNav({
	position: this.navigation,
	selectedColor: this.navSelectedColor,
	selectedBackground: this.navSelectedBackgroundColor,
	onclick: function(letter) {
	    if (self.isVerticalLayout()) {
		self.getListings(letter);
	    }
	    else {	//Remember the sort column and direction.
		var sortColumn = $(".sorting_asc");		//Get column that is sorted in ascending order.
    
		self.sortIndex = $(".dataTables_scrollHead th").index($(".sorting_asc"));
		
		if (self.sortIndex == -1) {
		    self.sortIndex = $(".dataTables_scrollHead th").index($(".sorting_desc"));
		    self.sortDirection = "desc";
		    
		    if (self.sortIndex == -1) {
			if (self.layout == "logos") {
			    self.sortIndex = 1;	//Default sort is on the second column.
			}
			else {
			    self.sortIndex = 0;
			}
	
			self.sortDirection = "asc";
		    }
		}
		else {
		    self.sortDirection = "asc";
		}
		
		if (sortColumn.length == 0) {		//No column is sorted in ascending order.
		    sortColumn = $(".sorting_desc");	//Get column that is sorted in descending order.
		    
		    if (sortColumn.length > 0) {	
			self.getListings(letter);
		    }
		}
		else {
		    self.getListings(letter);
		}
	    }
	}
    });
}
Directory.prototype.getListings = function(letter) {
    var self = this,
	queryString = "select *",
	settings = null;
    
    if ((letter != "all") && (this.navigation != "none")) {
	var column = "A";
	
	if (this.layout == "logos" && !this.isVerticalLayout()) {	//Logo is in the first column.
	    if ($(".dataTables_scrollHeadInner .sorting_asc").length == 1) {
		column = $(".dataTables_scrollHeadInner .sorting_asc").attr("id");
	    }
	    else if ($(".dataTables_scrollHeadInner .sorting_desc").length == 1) {
		column = $(".dataTables_scrollHeadInner .sorting_desc").attr("id");
	    }
	    else {
		column = "B";
	    }
	}
	
	//Case insensitive search.
	queryString = queryString + " where " + column + " starts with upper('" + letter + "') or " + column + " starts with lower('" + letter + "')";
    }
    
    if (this.layoutURL) {
	settings = {
	    url: this.url,
	    refreshInterval: this.interval,
	    queryString: queryString,
	    callback: function(data) {
		self.data = data;
		self.showListings.call(self);
	    }
	};
    }
    else {
	settings = {
	    url: this.url,
	    refreshInterval: this.interval,
	    queryString: queryString,
	    callback: function(data) {
		self.data = data;
		self.populateLayout.call(self);
	    }
	};
    }
    
    getFilteredData(settings);
}
Directory.prototype.showListings = function() {
    var $page = $("<div class='page'>");
	
    this.cols = [];
    this.imagesLoaded = 0;
    
    //Remove old content.
    if (!this.isLoading) {
	$("#scrollContainer").infiniteScroll.pause();
	$("#scrollContainer").empty();
    }
    
    //Display a message if no data was found.
    if (this.data.getNumberOfRows() == 0) {
	$("#scrollContainer").append("<div class='noData'>No matching records found</div>");
    }
    else {
	$("#scrollContainer").append($page);
	
	for (var col = 0; col < this.data.getNumberOfColumns(); col++) {
	    this.cols.push(this.data.getColumnId(col));
	}
	    
	for (var row = 0; row < this.data.getNumberOfRows(); row++) {
	    $page.append(this.directoryListing);
	    
	    //Calculate number of images that need to be loaded.
	    if (row == 0) {
		this.imageTotal = ($(".image").length * this.data.getNumberOfRows()) + ($(".qrCode").length * this.data.getNumberOfRows());
	    }
	    
	    for (var col = 0; col < this.data.getNumberOfColumns(); col++) {
		var $cell = $page.find("." + this.cols[col] + ":last");
		
		if ($cell.length > 0) {
		    if (($cell.hasClass("image")) || ($cell.hasClass("qrCode"))) {	//Show an image.
			if (this.data.getValue(row, col)) {
			    this.loadImage(this.data.getValue(row, col), $cell);
			}
			else {
			    this.imageTotal--;
			}
		    }
		    else {
			if (this.data.getColumnType(col) == "date") {
			    $cell.html(this.data.getFormattedValue(row, col));
			}
			else {	//Text
			    $cell.html(this.data.getValue(row, col));
			}
		    }
		}
	    }
	}
	
	$(".details").css({
	    "padding-top": this.spacing,
	    "padding-bottom": this.spacing
	});
	
	$(".listing:odd").addClass("odd");
	$(".listing:even").addClass("even");
    }
    
    if (this.imageTotal == 0) {
	$("#scrollContainer").infiniteScroll({
	    scrollBy: this.scrollBy,
	    direction: prefs.getString("scrollDirection"),
	    duration: prefs.getInt("duration") * 1000,
	    swipingTimeout: prefs.getInt("interactivityTimeout") * 1000
	});
	
	if (this.isLoading) {
	    this.isLoading = false;
	    readyEvent();
	}
	else {
	    $("#scrollContainer").infiniteScroll.start();
	}
    } 
}
Directory.prototype.loadImage = function(url, $image) {
    var self = this,
	img = new Image();
    
    img.onload = function () {
	$image.append(this);
	self.onImageLoaded();
    }
    
    img.onerror = function() {
	$image.hide();
	self.onImageLoaded();
    }

    img.src = url;
}
Directory.prototype.onImageLoaded = function() {
    this.imagesLoaded++;
    
    if (this.imagesLoaded == this.imageTotal) {
	$("#scrollContainer").infiniteScroll({
	    scrollBy: this.scrollBy,
	    direction: prefs.getString("scrollDirection"),
	    duration: prefs.getInt("duration") * 1000,
	    swipingTimeout: prefs.getInt("interactivityTimeout") * 1000
	});
	
	if (this.isLoading) {
	    this.isLoading = false;
	    readyEvent();
	}
	else {
	    $("#scrollContainer").infiniteScroll.start();
	}
    }
}
Directory.prototype.loadLogo = function(row, url) {
    var self = this,
	img = new Image();
    
    img.onload = function () {
	var $image = $(".item").eq(row).find(".image");
	
	if ($image) {
	    $image.css("background-image", "url(" + this.src + ")");
	}
	
	self.onLogoLoaded();
    }
    
    img.onerror = function() {
	self.onLogoLoaded();
    }

    img.src = url;
}
Directory.prototype.onLogoLoaded = function() {
    this.imagesLoaded++;
    
    if (this.imagesLoaded == this.imageTotal) {
	this.init();
    }
}
Directory.prototype.populateLayout = function() {
    var $table = $("<table id='data' class='page'>"),
	numRows = this.data.getNumberOfRows(),
	images = [];
    
    $("#scrollContainer").hide();
    
    //Remove old content.
    if (!this.isLoading) {
	$(".dataTables_scrollBody").infiniteScroll.pause();
	$("#scrollContainer").empty();
    }
    
    $("#scrollContainer").append($table);
    
    //Add headings.
    if (this.data.getNumberOfColumns() > 0) {
	this.renderHeadings();
    }
    
    //Add rows.
    for (var row = 0; row < numRows; row++) {
	this.renderRow(row);
    }
    
    if (this.layout == "logos") {
	this.imagesLoaded = 0;
	this.imageTotal = 0;
	
	if (numRows > 0) {
	    for (var row = 0; row < numRows; row++) {
		if (this.data.getValue(row, 0)) {
		    this.imageTotal++;
		}
	    }
    
	    for (var row = 0; row < numRows; row++) {
		if (this.data.getValue(row, 0)) {
		    this.loadLogo(row, this.data.getValue(row, 0));  
		}
	    }
	}
	else {	//No rows
	    this.init();
	}
    }
    else {
	this.init();
    }
}
Directory.prototype.init = function() {
    var sortConfig = {
	"aaSorting": [[this.sortIndex, this.sortDirection]],
	"bDestroy": true,
	"bFilter": false,
	"bInfo": false,
	"bLengthChange": false,
	"bPaginate": false,
	"sScrollY": "500px"	//Needed just to force table structure conducive to sorting.
    };
    
    //Logo column is not sortable.
    if (this.layout == "logos") {
	sortConfig.aoColumnDefs = [{"bSortable": false, "aTargets": [0]}];
    }
    
    $("#scrollContainer").show();
    $("#data").dataTable(sortConfig);
    
    if (!this.hasHeadings()) {
	$(".dataTables_wrapper table thead").css("display", "none");
    }
    
    $("td").css({
	"padding-top": this.spacing,
	"padding-bottom": this.spacing
    });
    
    if (this.isLoading) {
	this.configureNavigation($("#container"));
    }
    
    $("#scrollContainer").height($("#container").outerHeight(true) - $("#nav").outerHeight(true));
    $(".dataTables_scrollBody").height($("#container").outerHeight(true) - $("#nav").outerHeight(true) - $(".dataTables_scrollHead").height());
    
    $(".dataTables_scrollBody").infiniteScroll({
	scrollBy: this.scrollBy,
	direction: prefs.getString("scrollDirection"),
	duration: prefs.getInt("duration") * 1000,
	swipingTimeout: prefs.getInt("interactivityTimeout") * 1000
    }); 
	
    if (this.isLoading) {
	this.isLoading = false;
	readyEvent();
    }
    else {
	$(".dataTables_scrollBody").infiniteScroll.start();
    }
}
Directory.prototype.hasHeadings = function() {
    var hasHeading = false;
    
    for (var col = 0; col < this.data.getNumberOfColumns(); col++) {
	var label = this.data.getColumnLabel(col);
	
	if ((label != null) && (label != "")) {
	    hasHeading = true;
	    break;
	}
    }
    
    return hasHeading;
}
Directory.prototype.renderHeadings = function() {
    var $tr = $("<tr>"),
	hasHeadings = this.hasHeadings();
    
    for (var col = 0; col < this.data.getNumberOfColumns(); col++) {
	var $th = $("<th id='" + this.data.getColumnId(col) + "'>");
	
	if (hasHeadings) {
	    $th.addClass("nowrap name_font-style");
	    $th.html(this.data.getColumnLabel(col));
	
	    //No sort indicator for the logo column.
	    if (this.layout == "logos" && col == 0) {
	    }
	    else {
		var $arrow = $("<div class='arrow'>"),
		    $indicator = $("<div class='indicator'>"),
		    $indicator1 = $("<div class='indicator1'>"),
		    $indicator2 = $("<div class='indicator2'>");
		    
		if (this.sortDirection == "asc") {
		    $arrow.html("&darr;");
		    $indicator1.html("A");
		    $indicator2.html("Z");
		}
		else {
		    $arrow.html("&uarr;");
		    $indicator1.html("Z");
		    $indicator2.html("A");
		}
		
		$th.append($arrow);
		$indicator.append($indicator1);
		$indicator.append($indicator2);
		$th.append($indicator);
	    }
	}
	
	$tr.append($th);
    }
    
    $("table").append($("<thead>").append($tr));
}
//Render each row of data.
Directory.prototype.renderRow = function(row) {
    var $tr = $("<tr class='item'>");
    
    for (var col = 0; col < this.data.getNumberOfColumns(); col++) {
	var value = "", style = "";
    
	value = this.data.getFormattedValue(row, col);
	style = this.data.getProperty(row, col, "style");
	
	//Strip out the font-family that holds an incorrect value.
	if (style) {
	    style = style.substring(0, style.indexOf("font-family:"));
	}
	
	if (col == 0 && this.layout =="logos") {
	    this.addLogo($tr, value);
	}
	else {
	    this.addCell($tr, value, style);
	}
    }

    $("table").append($tr);
}
Directory.prototype.addLogo = function($tr, value) {
    var $td = $("<td class='imageContainer'>");
    
    $td.append("<div class='image'></div>");
    $tr.append($td);
}
Directory.prototype.addCell = function($tr, value, style) {
    var $td = $("<td>");
    
    if (style) {
	$td.attr("style", style);
    }
    
    if (this.showSeparator) {
	$td.attr("class", "separator");
    }
    
    $td.addClass("description_font-style");
    $td.html(value);
    $tr.append($td);
}
//Set a timer that will expire if there is no user interaction.
Directory.prototype.setTimer = function() {
    var self = this;
    
    $.idleTimer(60000);
    $(document).bind("idle.idleTimer", function() {
	$.idleTimer("destroy");
	self.showTab(self.tabIDs[0]);	//Go to Today.
    });
}
Directory.prototype.isVerticalLayout = function() {
    return (this.navigation == "left") || (this.navigation == "right");
}
Directory.prototype.play = function() {
    if (this.isVerticalLayout()) {
	$("#scrollContainer").infiniteScroll.start();
    }
    else {
	$(".dataTables_scrollBody").infiniteScroll.start();
    }
}
Directory.prototype.pause = function() {
    if (this.isVerticalLayout()) {
	$("#scrollContainer").infiniteScroll.pause();
    }
    else {
	$(".dataTables_scrollBody").infiniteScroll.pause();
    }
}